package middleware

import (
	"crypto/md5"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gotomicro/ego/core/elog"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

// SecretAuth 安全秘钥验证
func SecretAuth(secretTable map[string]string) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 处理接口验证
		// 1. 判断请求方法，如果不是HTTP POST直接返回错误
		if c.Request.Method != "POST" {
			elog.Error("SecretAuth: required POST, but got other", elog.Any("Method", c.Request.Method))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "错误的请求方法"})
			c.Abort()
			return
		}

		// 获取AppID和AppSecretKey
		appID := c.PostForm("appid")
		if appID == "" {
			elog.Error("SecretAuth: appID is empty")
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "AppID为空"})
			c.Abort()
			return
		}
		appSecret, ok := secretTable[appID]
		if !ok {
			elog.Error("SecretAuth: AppID未注册", elog.Any("secretTable", secretTable))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "AppID未注册"})
			c.Abort()
			return
		}

		// 校验请求时间
		timestampStr := c.PostForm("timestamp")
		timestamp, err := strconv.Atoi(timestampStr)
		if err != nil {
			elog.Error("SecretAuth: 请求时间戳非法", elog.Any("timestamp", timestampStr))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "请求时间戳非法"})
			c.Abort()
			return
		}
		nowUnixTime := time.Now().Unix()
		if nowUnixTime-int64(timestamp) > 600 {
			elog.Error("SecretAuth: 请求超时", elog.Any("timestamp", timestampStr), elog.Any("now", nowUnixTime))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "请求超时"})
			c.Abort()
			return
		}

		// 2. 获取接口参数
		m, err := ToMap(c.Request.Form)
		if err != nil {
			elog.Error("SecretAuth: err", elog.FieldErr(err))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": err.Error()})
			c.Abort()
			return
		}

		// 3. 获取请求签名、计算签名以及验证2者是否合法
		reqSign := c.PostForm("sign")
		// 3. 对接口参数进行验证 - 目前限制只能使用HTTP GET或者POST方式
		checkSign := CalculcateSign(m, appSecret)
		if reqSign != checkSign {
			elog.Error("SecretAuth: 验证请求非法", elog.Any("reqSign", reqSign), elog.Any("checkSign", checkSign))
			c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "验证请求非法"})
			c.Abort()
			return
		}

		c.Next()
	}

}

// CalculcateSign 生成签名
func CalculcateSign(m map[string]string, secretKey string) string {
	// 对签名字段名进行排序
	mKeys := make([]string, 0)
	for key := range m {
		mKeys = append(mKeys, key)
	}
	sort.Strings(mKeys)

	// 组合签名原始字符串
	// 以k1=value&k2=value&k3=value, 在原始字符串里面最后加key=xxx
	Signs := make([]string, 0)
	for _, key := range mKeys {
		// 如果是sign是，排除签名
		lowerKey := strings.ToLower(key)
		if lowerKey == "sign" {
			continue
		}

		// Value不存在，排除签名
		value, ok := m[key]
		if !ok {
			continue
		}

		// Value 为空，排查签名
		valueStr := fmt.Sprintf("%v", value)
		if valueStr == "" {
			continue
		}

		tmpSign := fmt.Sprintf("%v=%v", lowerKey, value)
		Signs = append(Signs, tmpSign)
	}

	Signs = append(Signs, fmt.Sprintf("key=%v", secretKey))
	rawSign := strings.Join(Signs, "&")

	w := md5.New()
	io.WriteString(w, rawSign)              //将str写入到w中
	md5str := fmt.Sprintf("%x", w.Sum(nil)) //w.Sum(nil)将w的hash转成[]byte格式
	return md5str
}

// ToMap 转换为map[string]string
func ToMap(values url.Values) (map[string]string, error) {

	m := make(map[string]string)
	for fk, fvs := range values {
		if len(fvs) >= 2 {
			return nil, fmt.Errorf("参数%v存在不确定值", fk)
		}
		m[fk] = fvs[0]
	}
	return m, nil
}
